using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Text;
using JetBrains.Annotations;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Ioc;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
using System.Linq;
#if TASKSUPPORT
using System.Threading.Tasks;
#endif

// ReSharper disable CheckNamespace
namespace MugenMvvmToolkit.ViewModels
// ReSharper restore CheckNamespace
{
    /// <summary>
    ///     Represents the extensions method for mvvm application.
    /// </summary>
    public static class MvvmExtensions
    {
        #region Ioc adapter extensions

        /// <summary>
        ///     Gets a root container.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="IIocAdapter" />
        /// </returns>
        [Pure]
        public static T GetRoot<T>([NotNull]this T iocAdapter) where T : IIocAdapter
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            iocAdapter.NotBeDisposed();
            if (iocAdapter.Parent == null) return iocAdapter;
            return (T)iocAdapter.Parent.GetRoot();
        }

        /// <summary>
        ///     Gets an instance of the specified service.
        /// </summary>
        /// <typeparam name="T">The specified service type.</typeparam>
        /// <param name="iocAdapter">
        ///     The specified <see cref="IIocAdapter" />.
        /// </param>
        /// <param name="parameters">The specified parameters.</param>
        /// <param name="name">The specified binding name.</param>
        /// <returns>An instance of T.</returns>
        [Pure]
        public static T Get<T>([NotNull]this IIocAdapter iocAdapter, string name = null, params IIocParameter[] parameters)
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            iocAdapter.NotBeDisposed();
            return (T)iocAdapter.Get(typeof(T), name, parameters);
        }

        /// <summary>
        ///     Gets all instances of the specified service.
        /// </summary>
        /// <param name="iocAdapter">
        ///     The specified <see cref="IIocAdapter" />.
        /// </param>
        /// <param name="name">The specified binding name.</param>
        /// <param name="parameters">The specified parameters.</param>
        /// <returns>An instance of the service.</returns>
        [Pure]
        public static IEnumerable<T> GetAll<T>([NotNull]this IIocAdapter iocAdapter, string name = null, params IIocParameter[] parameters)
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            iocAdapter.NotBeDisposed();
            return iocAdapter.GetAll(typeof(T), name, parameters).Cast<T>();
        }

        /// <summary>
        ///     Indicates that the service should be bound to the specified type.
        /// </summary>
        /// <typeparam name="T">The specified service type.</typeparam>
        /// <typeparam name="TTypeTo">The specified to type</typeparam>
        /// <param name="iocAdapter">
        ///     The specified <see cref="IIocAdapter" />.
        /// </param>
        /// <param name="dependencyLifecycle">
        ///     Specified <see cref="DependencyLifecycle" />
        /// </param>
        /// <param name="name">The specified binding name.</param>
        public static void Bind<T, TTypeTo>([NotNull]this IIocAdapter iocAdapter, DependencyLifecycle dependencyLifecycle,
                                            string name = null)
            where TTypeTo : T
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            iocAdapter.NotBeDisposed();
            iocAdapter.Bind(typeof(T), typeof(TTypeTo), dependencyLifecycle, name);
        }

        /// <summary>
        ///     Indicates that the service should be bound to the specified constant value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="iocAdapter">
        ///     The specified <see cref="IIocAdapter" />.
        /// </param>
        /// <param name="constValue">The specified constant value.</param>
        /// <param name="name">The specified binding name.</param>
        public static void BindToConstant<T>([NotNull]this IIocAdapter iocAdapter, T constValue, string name = null)
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            iocAdapter.NotBeDisposed();
            iocAdapter.BindToConstant(typeof(T), constValue, name);
        }

        /// <summary>
        ///     Indicates that the service should be bound to the specified constant value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="iocAdapter">
        ///     The specified <see cref="IIocAdapter" />.
        /// </param>
        /// <param name="getBinding">The specified binding value.</param>
        internal static void BindToBindingInfo<T>(this IIocAdapter iocAdapter, Func<BindingInfo<T>> getBinding)
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            Should.NotBeNull(getBinding, "getBinding");
            iocAdapter.NotBeDisposed();

            if (iocAdapter.CanResolve(typeof(T)))
                return;
            var binding = getBinding();
            if (binding.IsEmpty) return;
            // ReSharper disable once CompareNonConstrainedGenericWithNull
            if (binding.Instance != null)
                iocAdapter.BindToConstant(typeof(T), binding.Instance);
            else
                iocAdapter.Bind(typeof(T), binding.Type, binding.Lifecycle);
        }

        /// <summary>
        ///     Unregisters all bindings with specified conditions for the specified service.
        /// </summary>
        /// <typeparam name="T">The specified service type.</typeparam>
        /// <param name="iocAdapter">
        ///     The specified <see cref="IIocAdapter" />.
        /// </param>
        public static void Unbind<T>([NotNull]this IIocAdapter iocAdapter)
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            iocAdapter.NotBeDisposed();
            iocAdapter.Unbind(typeof(T));
        }

        /// <summary>
        ///     Gets an instance of the specified service.
        /// </summary>
        /// <typeparam name="T">The specified service type.</typeparam>
        /// <param name="serviceProvider">
        ///     The specified <see cref="IServiceProvider" />.
        /// </param>
        /// <returns>An instance of T.</returns>
        [Pure]
        public static T GetService<T>([NotNull]this IServiceProvider serviceProvider)
        {
            Should.NotBeNull(serviceProvider, "serviceProvider");
            return (T)serviceProvider.GetService(typeof(T));
        }

        #endregion

        #region Exception extension

        /// <summary>
        ///     Flatten the exception and inner exception data.
        /// </summary>
        /// <param name="exception">Exception</param>
        /// <param name="message">Any string prefix to add</param>
        /// <param name="includeStackTrace">True to include stack trace at end</param>
        /// <returns>String with Message and all InnerException messages appended together</returns>
        [Pure]
        public static string Flatten([NotNull]this Exception exception, string message = "", bool includeStackTrace = false)
        {
            Should.NotBeNull(exception, "exception");
            var sb = new StringBuilder(message);
            Exception current = exception;
            while (current != null)
            {
                sb.AppendLine(current.Message);
                if (includeStackTrace)
                    sb.Append(exception.StackTrace);

                current = current.InnerException;
                if (current != null && includeStackTrace)
                    sb.AppendLine();
            }

            return sb.ToString();
        }

        #endregion

        #region String extensions

        /// <summary>
        /// Returns a value indicating whether the specified <see cref="T:System.String"/> object occurs within this string.
        /// </summary>
        /// <param name="source">The specified source string.</param>
        /// <param name="value">The string to seek. </param>
        /// <param name="stringComparison">The specified <see cref="StringComparison"/>.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="value"/> is null. </exception>
        /// <returns>
        /// true if the <paramref name="value"/> parameter occurs within this string, or if <paramref name="value"/> is the empty string (""); otherwise, false.
        /// </returns>
        [Pure]
        public static bool Contains([NotNull]this string source, [NotNull]string value, StringComparison stringComparison)
        {
            return source.IndexOf(value, stringComparison) >= 0;
        }

        /// <summary>
        /// Returns a value indicating whether the specified <see cref="T:System.String"/> object occurs within this string. Not throws an exception.
        /// </summary>
        /// <param name="source">The specified source string.</param>
        /// <param name="value">The string to seek. </param>
        /// <param name="stringComparison">The specified <see cref="StringComparison"/>.</param>
        /// <returns>
        /// true if the <paramref name="value"/> parameter occurs within this string, or if <paramref name="value"/> is the empty string (""); otherwise, false.
        /// </returns>
        [Pure]
        public static bool SafeContains([CanBeNull]this string source, [CanBeNull] string value, StringComparison stringComparison = StringComparison.CurrentCultureIgnoreCase)
        {
            if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(value))
                return false;
            return source.IndexOf(value, stringComparison) >= 0;
        }

        #endregion

        #region Task extensions

#if TASKSUPPORT
        /// <summary>
        /// Creates a <see cref="T:System.Threading.Tasks.Task`1"/> that's completed successfully with the specified result.
        /// </summary>        
        /// <returns>
        /// The successfully completed task.
        /// </returns>
        /// <param name="result">The result to store into the completed task.</param><typeparam name="TResult">The type of the result returned by the task.</typeparam>
        [Pure]
        public static Task<TResult> FromResult<TResult>(TResult result)
        {
#if NET45
            return Task.FromResult(result);
#else
            var tcs = new TaskCompletionSource<TResult>();
            tcs.SetResult(result);
            return tcs.Task;
#endif
        }

        /// <summary>
        /// Invokes a task and wrap it to busy indicator.
        /// </summary>
        /// <param name="task">The specified <see cref="Task"/>.</param>
        /// <param name="viewModel">The specified <see cref="IViewModel"/>.</param>
        /// <param name="message">The specified message for the <c>BusyMessage</c> property.</param>
        /// <param name="handleExceptions">Responsible for handling errors if true errors will be processed using the <see cref="ITaskExceptionHandler"/> interface; otherwise false</param>
        public static Task WithBusyIndicator([NotNull]this Task task, [NotNull] IViewModel viewModel, bool handleExceptions, object message = null)
        {
            return task.WithBusyIndicator(viewModel, message, handleExceptions);
        }

        /// <summary>
        /// Invokes a task and wrap it to busy indicator.
        /// </summary>
        /// <param name="task">The specified <see cref="Task"/>.</param>
        /// <param name="viewModel">The specified <see cref="IViewModel"/>.</param>
        /// <param name="message">The specified message for the <c>BusyMessage</c> property.</param>
        /// <param name="handleExceptions">Responsible for handling errors if true errors will be processed using the <see cref="ITaskExceptionHandler"/> interface; otherwise false</param>
        public static Task<T> WithBusyIndicator<T>([NotNull]this Task<T> task, [NotNull] IViewModel viewModel, bool handleExceptions, object message = null)
        {
            return task.WithBusyIndicator(viewModel, message, handleExceptions);
        }

        /// <summary>
        /// Invokes a task and wrap it to busy indicator.
        /// </summary>
        /// <param name="task">The specified <see cref="Task"/>.</param>
        /// <param name="viewModel">The specified <see cref="IViewModel"/>.</param>
        /// <param name="message">The specified message for the <c>BusyMessage</c> property.</param>
        /// <param name="handleExceptions">Responsible for handling errors if true errors will be processed using the <see cref="ITaskExceptionHandler"/> interface; otherwise false</param>
        public static Task WithBusyIndicator([NotNull]this Task task, [NotNull] IViewModel viewModel, object message = null, bool? handleExceptions = null)
        {
            Should.NotBeNull(task, "task");
            Should.NotBeNull(viewModel, "viewModel");
            var isCompleted = task.IsCompleted;
            Guid? beginBusy = null;
            if (!isCompleted)
                beginBusy = viewModel.BeginBusy(message);
            return task.ContinueWith(task1 =>
                                     {
                                         if (beginBusy.HasValue)
                                             viewModel.EndBusy(beginBusy.Value);
                                         handleExceptions = handleExceptions
                                             .GetValueOrDefault(GlobalSettings.HandleTaskExceptionWithBusyIndicator);
                                         if (handleExceptions.Value && (viewModel.IsInitialized && !viewModel.IsDisposed))
                                             TryHandleTaskException(task, viewModel, viewModel.IocAdapter);
                                     }, viewModel.DisposeCancellationToken, task.GetContinuationOptions(), TaskScheduler.Current);
        }

        /// <summary>
        /// Invokes a task and wrap it to busy indicator.
        /// </summary>
        /// <param name="task">The specified <see cref="Task"/>.</param>
        /// <param name="viewModel">The specified <see cref="IViewModel"/>.</param>
        /// <param name="message">The specified message for the <c>BusyMessage</c> property.</param>
        /// <param name="handleExceptions">Responsible for handling errors if true errors will be processed using the <see cref="ITaskExceptionHandler"/> interface; otherwise false</param>
        public static Task<T> WithBusyIndicator<T>([NotNull]this Task<T> task, [NotNull] IViewModel viewModel, object message = null, bool? handleExceptions = null)
        {
            Should.NotBeNull(task, "task");
            Should.NotBeNull(viewModel, "viewModel");
            var isCompleted = task.IsCompleted;
            Guid? beginBusy = null;
            if (!isCompleted)
                beginBusy = viewModel.BeginBusy(message);
            return task.ContinueWith(task1 =>
                                     {
                                         try
                                         {
                                             return task1.Result;
                                         }
                                         finally
                                         {
                                             if (beginBusy.HasValue)
                                                 viewModel.EndBusy(beginBusy.Value);
                                             handleExceptions = handleExceptions
                                             .GetValueOrDefault(GlobalSettings.HandleTaskExceptionWithBusyIndicator);
                                             if (handleExceptions.Value && (viewModel.IsInitialized && !viewModel.IsDisposed))
                                                 TryHandleTaskException(task, viewModel, viewModel.IocAdapter);
                                         }
                                     }, viewModel.DisposeCancellationToken, task.GetContinuationOptions(), TaskScheduler.Current);
        }

        internal static Task WithTaskExceptionHandler(this Task task, IViewModel viewModel)
        {
            Should.NotBeNull(task, "task");
            Should.NotBeNull(viewModel, "viewModel");
            if (!viewModel.IsInitialized || viewModel.IsDisposed)
                return task;
            return task.ContinueWith(task1 => TryHandleTaskException(task, viewModel, viewModel.IocAdapter), viewModel.DisposeCancellationToken, task.GetContinuationOptions(), TaskScheduler.Current);
        }

        internal static Task WithTaskExceptionHandler(this Task task, object sender, IIocAdapter iocAdapter)
        {
            Should.NotBeNull(task, "task");
            if (iocAdapter == null || iocAdapter.IsDisposed)
                return task;
            return task.ContinueWith(task1 => TryHandleTaskException(task, sender, iocAdapter), task.GetContinuationOptions());
        }

        internal static TaskContinuationOptions GetContinuationOptions(this Task task)
        {
            return task.IsCompleted ? TaskContinuationOptions.ExecuteSynchronously : TaskContinuationOptions.None;
        }

        private static void TryHandleTaskException(this Task task, object sender, IIocAdapter iocAdapter)
        {
            if ((task.IsFaulted || task.IsCanceled) && iocAdapter != null && !iocAdapter.IsDisposed &&
                iocAdapter.CanResolve(typeof(ITaskExceptionHandler)))
            {
                foreach (var handler in iocAdapter.GetAll<ITaskExceptionHandler>())
                    handler.Handle(sender, task.Status, task.Exception);
            }
        }
#else
        /// <summary>
        ///     Creates a <see cref="IOperationResult{T}" /> that's completed successfully with the specified result.
        /// </summary>
        /// <returns>
        ///     The successfully completed operation.
        /// </returns>
        /// <param name="result">The result to store into the completed operation.</param>
        /// <typeparam name="TResult">The type of the result returned by the operation.</typeparam>
        [Pure]
        public static IOperationResult<TResult> FromResult<TResult>(TResult result)
        {
            return OperationResult.FromResult(result);
        }

        internal static IOperationResult WithTaskExceptionHandler(this IOperationResult task, [NotNull] IViewModel viewModel)
        {
            Should.NotBeNull(task, "task");
            Should.NotBeNull(viewModel, "viewModel");
            if (!viewModel.IsDisposed)
                task.BeginWait(result => TryHandleTaskException(result, viewModel, viewModel.IocAdapter));
            return task;
        }

        internal static IOperationResult WithTaskExceptionHandler(this IOperationResult task, object sender, IIocAdapter iocAdapter)
        {
            Should.NotBeNull(task, "task");
            if (iocAdapter != null && !iocAdapter.IsDisposed)
                task.BeginWait(result => TryHandleTaskException(result, sender, iocAdapter));
            return task;
        }

        private static void TryHandleTaskException(IOperationResult task, object sender, IIocAdapter iocAdapter)
        {
            if (task.IsFaulted && iocAdapter != null && !iocAdapter.IsDisposed &&
                iocAdapter.CanResolve(typeof(IOperationExceptionHandler)))
            {
                foreach (var handler in iocAdapter.GetAll<IOperationExceptionHandler>())
                    handler.Handle(sender, task.Status, task.Exception);
            }
        }

#endif

        #endregion

        #region Collection extensions

        /// <summary>
        /// Returns <c>true</c> if and only if <paramref name="enumerable"/> is empty (has no elements) or null.
        /// </summary>        
        [Pure]
        public static bool IsNullOrEmpty(this IEnumerable enumerable)
        {
            return enumerable == null || enumerable.IsEmpty();
        }

        /// <summary>
        /// Returns <c>true</c> if and only if <paramref name="enumerable"/> is empty (has no elements).
        /// </summary>        
        [Pure]
        public static bool IsEmpty([NotNull]this IEnumerable enumerable)
        {
            Should.NotBeNull(enumerable, "enumerable");
            var collection = enumerable as ICollection;
            if (collection != null)
                return collection.Count == 0;
            IEnumerator enumerator = enumerable.GetEnumerator();
            try
            {
                return !enumerator.MoveNext();
            }
            finally
            {
                var disposable = enumerator as IDisposable;
                if (disposable != null)
                    disposable.Dispose();
            }
        }

        /// <summary>
        ///     Enumerates a collection and executes a predicate against each item
        /// </summary>
        /// <typeparam name="T">Type of elements</typeparam>
        /// <param name="collection">The specified collection.</param>
        /// <param name="action">Action to execute on each element</param>
        public static void ForEach<T>([NotNull]this IEnumerable<T> collection, [NotNull] Action<T> action)
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(action, "action");
            var array = collection as T[];
            if (array != null)
            {
                // ReSharper disable once ForCanBeConvertedToForeach
                for (int i = 0; i < array.Length; i++)
                {
                    action(array[i]);
                }
                return;
            }
#if !NETFX_CORE
            var list = collection as List<T>;
            if (list != null)
                list.ForEach(action);
            else
#endif
                foreach (T o in collection)
                    action(o);
        }

        /// <summary>
        ///     Adds a range of IEnumerable collection to an existing Collection.
        /// </summary>
        /// <typeparam name="T">Type of collection</typeparam>
        /// <param name="collection">The specified collection.</param>
        /// <param name="items">Items to add</param>
        public static void AddRange<T>([NotNull]this ICollection<T> collection, [NotNull] IEnumerable<T> items)
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(items, "items");
            var list = collection as List<T>;
            if (list != null)
            {
                list.AddRange(items);
                return;
            }
            var notifiableCollection = collection as SyncronizedNotifiableCollection<T>;
            if (notifiableCollection != null)
            {
                notifiableCollection.AddRange(items);
                return;
            }
            foreach (T item in items)
                collection.Add(item);
        }

        /// <summary>
        ///     Adds all items from the source into the target dictionary.
        /// </summary>
        /// <typeparam name="TKey">The type of the key.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="target">The target.</param>
        /// <param name="source">The source.</param>
        public static void AddRange<TKey, TValue>([NotNull]this IDictionary<TKey, TValue> target, [NotNull] IEnumerable<KeyValuePair<TKey, TValue>> source)
        {
            Should.NotBeNull(target, "target");
            Should.NotBeNull(source, "source");
            foreach (var keyValuePair in source)
                target[keyValuePair.Key] = keyValuePair.Value;
        }

        /// <summary>
        ///     Removes a range of IEnumerable collection to an existing Collection.
        /// </summary>
        /// <typeparam name="T">Type of collection</typeparam>
        /// <param name="collection">The specified collection.</param>
        /// <param name="items">Items to remove</param>
        public static void RemoveRange<T>([NotNull]this ICollection<T> collection, [NotNull] IEnumerable<T> items)
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(items, "items");
            var notifiableCollection = collection as SyncronizedNotifiableCollection<T>;
            if (notifiableCollection != null)
            {
                notifiableCollection.RemoveRange(items);
                return;
            }
            foreach (T item in items)
                collection.Remove(item);
        }

        /// <summary>
        /// Creates a <see cref="SyncronizedNotifiableCollection{T}"/> from a notifiable collection.
        /// </summary>
        /// <typeparam name="T">The type of collection.</typeparam>
        /// <param name="collection">The specified collection.</param>
        /// <param name="threadManager">The specified <see cref="IThreadManager"/>.</param>
        /// <returns>An instance of <see cref="SyncronizedNotifiableCollection{T}"/>.</returns>
        [Pure]
        public static SyncronizedNotifiableCollection<T> ToSyncronizedCollection<T>([NotNull]this IEnumerable<T> collection, [NotNull]IThreadManager threadManager = null)
        {
            Should.NotBeNull(collection, "collection");
            return collection as SyncronizedNotifiableCollection<T> ??
                   new SyncronizedNotifiableCollection<T>(collection, threadManager);
        }

        /// <summary>
        /// Creates a <see cref="SyncronizedNotifiableCollection{T}"/> from a notifiable collection.
        /// </summary>
        /// <typeparam name="T">The type of collection.</typeparam>
        /// <param name="list">The specified collection.</param>
        /// <param name="threadManager">The specified <see cref="IThreadManager"/>.</param>
        /// <returns>An instance of <see cref="SyncronizedNotifiableCollection{T}"/>.</returns>
        [Pure]
        public static SyncronizedNotifiableCollection<T> ToSyncronizedCollection<T>([NotNull]this IList<T> list, [NotNull] IThreadManager threadManager = null)
        {
            Should.NotBeNull(list, "list");
            return list as SyncronizedNotifiableCollection<T> ??
                   new SyncronizedNotifiableCollection<T>(list, threadManager);
        }

        #endregion

        #region Extensions

        /// <summary>
        /// Creates an instance of IIocAdapter using activation policy and the parent view model if any.
        /// </summary>
        public static IIocAdapter CreateViewModelIocAdapter([NotNull]this IActivationPolicy activationPolicy, [NotNull] IIocAdapter rootIocAdapter, IViewModel parentViewModel, bool bindIocAdapter)
        {
            IIocAdapter iocAdapter;
            if (!activationPolicy.UseParentIocAdapter || parentViewModel == null)
                iocAdapter = rootIocAdapter.GetRoot().CreateChild();
            else
                iocAdapter = parentViewModel.IocAdapter.CreateChild();
            // ReSharper disable once RedundantTypeArgumentsOfMethod
            if (bindIocAdapter)
                iocAdapter.BindToConstant<IIocAdapter>(iocAdapter);
            return iocAdapter;
        }

        /// <summary>
        /// Checks whether the properties are equal.
        /// </summary>
        /// <param name="propertyName">The specified property name.</param>
        /// <param name="getProperty">The expression to get property.</param>
        /// <returns>If true property is equal, otherwise false.</returns>
        [Pure]
        public static bool PropertyNameEqual<T>(string propertyName, [NotNull] Expression<Func<T, object>> getProperty)
        {
            return MvvmUtilsInternal.ParsePropertyExpression(getProperty) == propertyName;
        }

        /// <summary>
        /// Checks whether the properties are equal.
        /// </summary>
        /// <param name="propertyChangedEvent">The specified property changed event.</param>
        /// <param name="getProperty">The expression to get property.</param>
        /// <returns>If true property is equal, otherwise false.</returns>
        [Pure]
        public static bool PropertyNameEqual<T>([NotNull]this PropertyChangedEventArgs propertyChangedEvent, [NotNull] Expression<Func<T, object>> getProperty)
        {
            Should.NotBeNull(propertyChangedEvent, "propertyChangedEvent");
            return MvvmUtilsInternal.ParsePropertyExpression(getProperty) == propertyChangedEvent.PropertyName;
        }

        /// <summary>
        /// Gets property name from the specified expression.
        /// </summary>
        /// <typeparam name="T">The type of model.</typeparam>
        /// <param name="expression">The specified expression.</param>
        /// <returns>An instance of string.</returns>
        [Pure]
        public static string GetPropertyName<T>([NotNull]Expression<Func<T, object>> expression)
        {
            return MvvmUtilsInternal.ParsePropertyExpression(expression);
        }

        /// <summary>
        /// Gets property name from the specified expression.
        /// </summary>
        /// <typeparam name="T">The type of model.</typeparam>
        /// <param name="item">The specified model.</param>
        /// <param name="expression">The specified expression.</param>
        /// <returns>An instance of string.</returns>
        [Pure]
        public static string GetPropertyName<T>([CanBeNull]T item, [NotNull]Expression<Func<T, object>> expression)
        {
            return MvvmUtilsInternal.ParsePropertyExpression(expression);
        }

        internal static bool HasFlag(this NotificationCollectionCountType countType,
            NotificationCollectionCountType value)
        {
            return (countType & value) == value;
        }

        internal static bool HasFlag(this HandleType handleType, HandleType value)
        {
            return (handleType & value) == value;
        }

        internal static bool HasFlag(this ListenType handleType, ListenType value)
        {
            return (handleType & value) == value;
        }

        #endregion
    }
}